﻿using System.Linq;
using CavinatorService.Model;
using CavinatorService.Model.ID3;
using NDatabase;
using System.Collections.Generic;
using CavinatorService.DAO;
using System;

namespace CavinatorService.Business
{
    /// <summary>
    /// Classe responsável pela lógica relacionada a ID3
    /// </summary>
    public class ID3Business
    {
        QuestionMapper questionMapper;
        ItemMapper itemsMapper;
        AnswerMapper answerMappper;
        public List<Item> Items { get; set; }
        public List<Question> Questions { get; set; }
        public List<Answer> Answers { get; set; }

        #region Constructors

        /// <summary>
        /// Construtor padrão
        /// </summary>
        public ID3Business()
        {
            questionMapper = new QuestionMapper();
            itemsMapper = new ItemMapper();
            answerMappper = new AnswerMapper();
            this.CriarArvore();
        }
        #endregion

        #region Methods

        /// <summary>
        /// Método para Carregar a ID3
        /// </summary>
        /// <returns>Nó raiz</returns>
        public Node CarregarArvore()
        {
            using (var db = OdbFactory.Open("Cavinator.db"))
            {
                var objectSet = db.QueryAndExecute<Node>();

                if (objectSet != null && objectSet.Count > 0)
                {
                    return objectSet.ElementAt(0);
                }
            }

            return new Node();

        }

        /// <summary>
        /// Método para Armazenar a ID3
        /// </summary>
        /// <param name="noRaiz">Nó raiz</param>
        public void ArmazenarArvore(Node noRaiz)
        {
            using (var db = OdbFactory.Open("Cavinator.db"))
            {
                db.Store(noRaiz);
            }
        }

        public Node CriarArvore()
        {
            Questions = questionMapper.Listar();
            /*Items = new List<Model.Item>();
            Answers = new List<Model.Answer>();*/
            Items = itemsMapper.Listar();
            Answers = answerMappper.Listar();

            #region Teste Local Aguardando inserção BD
            /*Answers.Add(new Answer() {Id=1, IdItem= 1, AnswerValue ="Mamífero", Question = Questions[0], IdQuestion = Questions[0].Id});
            Answers.Add(new Answer() {Id=2, IdItem= 1, AnswerValue ="Cidade", Question = Questions[1], IdQuestion = Questions[1].Id});
            Answers.Add(new Answer() {Id=3, IdItem= 1, AnswerValue ="Lisa", Question = Questions[2], IdQuestion = Questions[2].Id});
            Answers.Add(new Answer() {Id=4, IdItem= 1, AnswerValue ="Onívoro", Question = Questions[3], IdQuestion = Questions[3].Id});
            Answers.Add(new Answer() {Id=5, IdItem= 1, AnswerValue ="Quatro", Question = Questions[4], IdQuestion = Questions[4].Id});
            Answers.Add(new Answer() {Id=6, IdItem= 1, AnswerValue ="Fala", Question = Questions[5], IdQuestion = Questions[5].Id});
            Answers.Add(new Answer() {Id=7, IdItem= 1, AnswerValue ="Sim", Question = Questions[6], IdQuestion = Questions[6].Id});
            Answers.Add(new Answer() {Id=8, IdItem= 1, AnswerValue ="Média", Question = Questions[7], IdQuestion = Questions[7].Id});

            Answers.Add(new Answer() { Id = 1, IdItem = 2, AnswerValue = "Mamífero", Question = Questions[0], IdQuestion = Questions[0].Id });
            Answers.Add(new Answer() { Id = 2, IdItem = 2, AnswerValue = "Cidade", Question = Questions[1], IdQuestion = Questions[1].Id });
            Answers.Add(new Answer() { Id = 3, IdItem = 2, AnswerValue = "Variada", Question = Questions[2], IdQuestion = Questions[2].Id });
            Answers.Add(new Answer() { Id = 4, IdItem = 2, AnswerValue = "Carnívoro", Question = Questions[3], IdQuestion = Questions[3].Id });
            Answers.Add(new Answer() { Id = 5, IdItem = 2, AnswerValue = "Quatro", Question = Questions[4], IdQuestion = Questions[4].Id });
            Answers.Add(new Answer() { Id = 6, IdItem = 2, AnswerValue = "Latido", Question = Questions[5], IdQuestion = Questions[5].Id });
            Answers.Add(new Answer() { Id = 7, IdItem = 2, AnswerValue = "Não", Question = Questions[6], IdQuestion = Questions[6].Id });
            Answers.Add(new Answer() { Id = 8, IdItem = 2, AnswerValue = "Média", Question = Questions[7], IdQuestion = Questions[7].Id });

            Answers.Add(new Answer() { Id = 1, IdItem = 3, AnswerValue = "Anfíbio", Question = Questions[0], IdQuestion = Questions[0].Id });
            Answers.Add(new Answer() { Id = 2, IdItem = 3, AnswerValue = "Água/Floresta", Question = Questions[1], IdQuestion = Questions[1].Id });
            Answers.Add(new Answer() { Id = 3, IdItem = 3, AnswerValue = "Variada", Question = Questions[2], IdQuestion = Questions[2].Id });
            Answers.Add(new Answer() { Id = 4, IdItem = 3, AnswerValue = "Carnívoro", Question = Questions[3], IdQuestion = Questions[3].Id });
            Answers.Add(new Answer() { Id = 5, IdItem = 3, AnswerValue = "Quatro", Question = Questions[4], IdQuestion = Questions[4].Id });
            Answers.Add(new Answer() { Id = 6, IdItem = 3, AnswerValue = "Coacha", Question = Questions[5], IdQuestion = Questions[5].Id });
            Answers.Add(new Answer() { Id = 7, IdItem = 3, AnswerValue = "Não", Question = Questions[6], IdQuestion = Questions[6].Id });
            Answers.Add(new Answer() { Id = 8, IdItem = 3, AnswerValue = "Média", Question = Questions[7], IdQuestion = Questions[7].Id });

            Answers.Add(new Answer() { Id = 1, IdItem = 4, AnswerValue = "Mamifero", Question = Questions[0], IdQuestion = Questions[0].Id });
            Answers.Add(new Answer() { Id = 2, IdItem = 4, AnswerValue = "Savana", Question = Questions[1], IdQuestion = Questions[1].Id });
            Answers.Add(new Answer() { Id = 3, IdItem = 4, AnswerValue = "Rajada", Question = Questions[2], IdQuestion = Questions[2].Id });
            Answers.Add(new Answer() { Id = 4, IdItem = 4, AnswerValue = "Carnívoro", Question = Questions[3], IdQuestion = Questions[3].Id });
            Answers.Add(new Answer() { Id = 5, IdItem = 4, AnswerValue = "Quatro", Question = Questions[4], IdQuestion = Questions[4].Id });
            Answers.Add(new Answer() { Id = 6, IdItem = 4, AnswerValue = "Rugido", Question = Questions[5], IdQuestion = Questions[5].Id });
            Answers.Add(new Answer() { Id = 7, IdItem = 4, AnswerValue = "Sim", Question = Questions[6], IdQuestion = Questions[6].Id });
            Answers.Add(new Answer() { Id = 8, IdItem = 4, AnswerValue = "Rápido", Question = Questions[7], IdQuestion = Questions[7].Id });

            Answers.Add(new Answer() { Id = 1, IdItem = 5, AnswerValue = "Ave", Question = Questions[0], IdQuestion = Questions[0].Id });
            Answers.Add(new Answer() { Id = 2, IdItem = 5, AnswerValue = "Cidade", Question = Questions[1], IdQuestion = Questions[1].Id });
            Answers.Add(new Answer() { Id = 3, IdItem = 5, AnswerValue = "Variada", Question = Questions[2], IdQuestion = Questions[2].Id });
            Answers.Add(new Answer() { Id = 4, IdItem = 5, AnswerValue = "Onívoro", Question = Questions[3], IdQuestion = Questions[3].Id });
            Answers.Add(new Answer() { Id = 5, IdItem = 5, AnswerValue = "Duas", Question = Questions[4], IdQuestion = Questions[4].Id });
            Answers.Add(new Answer() { Id = 6, IdItem = 5, AnswerValue = "Cacareja", Question = Questions[5], IdQuestion = Questions[5].Id });
            Answers.Add(new Answer() { Id = 7, IdItem = 5, AnswerValue = "Sim", Question = Questions[6], IdQuestion = Questions[6].Id });
            Answers.Add(new Answer() { Id = 8, IdItem = 5, AnswerValue = "Média", Question = Questions[7], IdQuestion = Questions[7].Id });

            Items.Add(new Model.Item() { Id = 1, Value = "Homem", Answers = new List<Model.Answer>() });
            Items.Add(new Model.Item() { Id = 2, Value = "Cachorro", Answers = new List<Model.Answer>() });
            Items.Add(new Model.Item() { Id = 3, Value = "Sapo", Answers = new List<Model.Answer>() });
            Items.Add(new Model.Item() { Id = 4, Value = "Tigre", Answers = new List<Model.Answer>() });
            Items.Add(new Model.Item() { Id = 5, Value = "Galinha", Answers = new List<Model.Answer>() });*/
            #endregion

            Node root = new Node();
            GerarArvore(root);
            return root;
        }

        private void GerarArvore(Node root, List<Answer> answers = null)
        {
            List<Item> Filtrados = new List<Model.Item>();
            List<int> IdItensFiltrados = Items.Select(i=> i.Id).ToList();
            if (answers == null) answers = new List<Answer>();
            List<Answer> Respostas = Answers;
            var Filtro = Answers;
            //IdItensFiltrados 
            //Pega o ID dos itens que atendem a pergunta e respostas já realizadas
            foreach (var ans in answers)
            {
                var Temp = Answers.Where(i => i.AnswerValue == ans.AnswerValue && i.IdQuestion == ans.IdQuestion).Select(i => i.IdItem).ToList();
                IdItensFiltrados = IdItensFiltrados.Where(i => Temp.Any(j=> j == i)).ToList();
                
            }
            //Pega os itens dos IDs verificados acima
            foreach (var item in IdItensFiltrados)
            {
                Filtrados.AddRange(Items.Where<Item>(i => i.Id == item).ToList());
            }
            //Condição de Parada, se for um nó folha add o item resposta ao nó
            if (Filtrados.Count <= 1 || QuantidadePerguntas(answers) == 0)
            {
                root.Items = Filtrados;
                return;
            } 
            Question melhorPergunta = CalcularMelhorPergunta(answers);
            //Busca as respostas da melhor pergunta)
            List<string> respMp = Answers.Where<Answer>(i=> i.IdQuestion == melhorPergunta.Id).Select(i => i.AnswerValue).Distinct().ToList();
            ///Preencher as informações do nó corrente
            foreach (var ans in respMp)
            {
                //Instancia um novo nó
                Node filho = new Node();
                //Adiciona as informações necessárias no nó corrente
                root.Arcs.Add(new Arc(filho, ans));
                root.Items = Filtrados;
                root.Question = melhorPergunta;
                //Clona a lista de resposta para modifica-la sem perder a referência
                List<Answer> novasRespostas = Clone(answers);
                novasRespostas.Add(new Answer() { Question = melhorPergunta, AnswerValue = ans, IdQuestion = melhorPergunta.Id });
                //Chama a recursão
                GerarArvore(filho, novasRespostas);
            }

            return;
        }

        private int QuantidadePerguntas(List<Model.Answer> answers)
        {
            return Questions.Where(i => !answers.Any(j => j.IdQuestion == i.Id)).ToList().Count;
        }

        private Question CalcularMelhorPergunta(List<Answer> answers)
        {
            double min = 1;
            int melhor = 0;
            double entropia;
            List<Item> Filtrados = Items;
            List<int> IdItensFiltrados = Items.Select(i => i.Id).ToList();
            //Pega o ID dos itens que atendem a pergunta e respostas já realizadas
            foreach (var ans in answers)
            {
                var Temp = Answers.Where(i => i.AnswerValue == ans.AnswerValue && i.IdQuestion == ans.IdQuestion).Select(i => i.IdItem).ToList();
                IdItensFiltrados = IdItensFiltrados.Where(i => Temp.Any(j => j == i)).ToList();
            }
            //count total de itens para o cálculo
            int count = IdItensFiltrados.Count;
            //Busca as perguntas que ainda não foram realizadas
            List<Question> Perguntas = PerguntasNaoFeitas(answers);
            //Para cada pergunta ele calcula a entropia e retorna a Pergunta de menor valor
            for (int i = 0; i < Perguntas.Count; i++)
            {
                entropia = CalculaEntropia(answers, Perguntas[i], count);
                if (i == 0)
                    min = entropia;
                if (entropia < min)
                {
                    min = entropia;
                    melhor = i;
                }
            }


            return Perguntas[melhor];
        }

        private double CalculaEntropia(List<Answer> answers, Question question, int count)
        {
            List<double> Entropias = new List<double>();
            //Lista as respostas possíveis
            List<string> RespostasDistinct = Answers.Where(i => i.IdQuestion == question.Id).Select(i => i.AnswerValue).Distinct().ToList();
            //Para cada resposta possível será calculada a entropia
            foreach (var ans in RespostasDistinct)
            {
                Entropias.Add(CalculaEntropia(answers, new Answer() { Question = question, IdQuestion = question.Id, AnswerValue = ans }, count));
            }
            return Entropias.Sum();

        }

        private double CalculaEntropia(List<Answer> answers, Answer ans, int countTotal)
        {
            List<Answer> novasRespostas = Clone(answers);
            novasRespostas.Add(ans);
            List<int> IdItensFiltrados = Items.Select(i => i.Id).ToList();
            //List<Item> ItensFiltrados = new List<Item>();
            List<Item> ItensFiltrados = Items;
            foreach (var a in novasRespostas)
            {
                //Identifica a pergunta, e verifica a resposta para o item em questão
                var Temp = Answers.Where(i => i.AnswerValue == a.AnswerValue && i.IdQuestion == a.IdQuestion).Select(i => i.IdItem).ToList();
                IdItensFiltrados = IdItensFiltrados.Where(i => Temp.Any(j => j == i)).ToList();
                //Filtrados = Items.Where<Item>(i => i.Answers.Find(j => j.Question.Id == a.Question.Id).AnswerValue == a.AnswerValue).ToList();
            }
           /* foreach (var item in IdItensFiltrados)
	        {
                ItensFiltrados = Items.Where<Item>(i => i.Id == item).ToList();
	        }*/

            int count = IdItensFiltrados.Count;
            if (count == 0) return 0;
            return (-1) * ((double)count / countTotal) * Math.Log(((double)count / countTotal), 2);

        }

        private List<Question> PerguntasNaoFeitas(List<Answer> answers)
        {
            return Questions.Where(i => !answers.Any(j => j.IdQuestion == i.Id)).ToList();
        }

        List<T> Clone<T>(IEnumerable<T> oldList)
        {
            return new List<T>(oldList);
        }

        /// <summary>
        /// Metodo para gerar a ID3
        /// </summary>
        public void GerarArvore()
        {
            //TODO: Danilo e Gustavo
            //Add your logic here
            Node raiz = CriarArvore();
            ArmazenarArvore(raiz);

        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="protocolo"></param>
        /// <param name="node">No atual da arvore</param>
        /// <returns></returns>
        public Protocol ObterProximaPergunta(ref Node node, Protocol protocolo)
        {

            //TODO: Ciarolo <- É CIRAOLO!!!! ò_ó
            //Add your logic here

            // Se o protocolo é null, é porque ele quer a primeira pergunta
            if (protocolo == null)
            {
                node = CarregarArvore();
                // Se o vetor de itens não foi inicializado, é porque o nó raiz não foi carregado corretamente.
                // Neste caso, regera a Árvore.
                if (node.Items.Count == 0)
                {
                    GerarArvore();
                    node = CarregarArvore();
                }
            }
            // Caso contrario, é preciso ver qual a próxima pergunta
            else
            {
                Answer a = protocolo.Answers[0];
                foreach (Arc arc in node.Arcs) {
                    if (arc.answer == a.AnswerValue)
                    {
                        node = arc.nodeTo;
                    }
                }
            }


            Protocol resposta = new Protocol();
            List<Answer> answers = new List<Answer>();
            // Se o numero de itens for diferente de 1, significa que ainda há arcos a serem percorridos.
            if (node.Items.Count != 1)
            {
                // Se a Lista de Arcos for diferente de 0, é porque há uma pergunta a ser feita.
                if (node.Arcs.Count != 0 || node.Items.Count == 0)
                {
                    resposta.Info = node.Question.Value;
                    foreach (Arc arc in node.Arcs)
                    {
                        // Gambiarra porque o Arco não tem o Objeto do tipo Answer, e sim uma String.
                        // E o protocolo pede um Objeto do tipo Answer, e não uma String.
                        // ¯\_(ツ)_/¯
                        Answer newAnswer = new Answer();
                        newAnswer.AnswerValue = arc.answer;
                        answers.Add(newAnswer);
                    }
                    resposta.IsAnswer = false;
                }
                // Caso contrario, é porque há dois ou mais itens num nó folha. Mostra todos.
                else
                {
                    resposta.Info = "";
                    Answer a = new Answer();
                    a.AnswerValue = "";
                    bool first = true;
                    foreach (Item it in node.Items)
                    {
                        if (!first) a.AnswerValue += " ou ";
                        else first = false;
                        a.AnswerValue += it.Value;
                    }
                    answers.Add(a);
                    resposta.IsAnswer = true;
                }
            }
            // Caso contrario, é porque chegamos na resposta.
            else
            {
                resposta.Info = "";
                Answer a = new Answer();
                a.AnswerValue = node.Items[0].Value;
                answers.Add(a);
                resposta.IsAnswer = true;
            }
            resposta.Answers = answers;
            resposta.IdQuestion = node.Question.Id;

            // Retorna o protocolo.
            return resposta;
        }
    }

    #endregion
}
